from dataclasses import dataclass
from typing import Callable

from my_logging import GetLogger, DEBUG

logger = GetLogger('dict_checker')
logger.setLevel(DEBUG)

def isIterable(l) -> bool:
    try:
        iter(l)
        return True
    except:
        return False


@dataclass
class Constrain():
    wanted_type : type
    constrain : Callable[..., bool]
    element_constrain : dict = None


def CheckDict(data_dict : dict, constrain_dict : dict[str, type | dict | Constrain], data_dict_name : str = 'root') -> bool:
    if not isinstance(data_dict, dict):
        logger.debug(f'Not pass as {data_dict_name} is not a dict.')
        return False

    for key, constrain in constrain_dict.items():
        if key not in data_dict.keys():
            logger.debug(f'Not pass as wanted key [{key}] not in {data_dict_name}.')
            return False

        if isinstance(constrain, type):
            if not (isinstance(data_dict[key], constrain)):
                logger.debug(f'Not pass as {data_dict_name}.{key} is not type {constrain}.')
                return False
        elif isinstance(constrain, Constrain):
            if not (isinstance(data_dict[key], constrain.wanted_type) and 
                    constrain.constrain(data_dict[key])
                ):
                logger.debug(f'Not pass as {data_dict_name}.{key} is not type {constrain} or not pass the constrain.')
                return False
            if constrain.element_constrain is not None:
                if not isIterable(data_dict[key]):
                    logger.debug(f'Not pass as {data_dict_name}.{key} is not iter-able.')
                    return False

                for i, element in enumerate(data_dict[key]):
                    if not CheckDict(
                        element, constrain.element_constrain, f'{data_dict_name}.{key}[{i}]'
                    ):
                        return False
        elif isinstance(constrain, dict):
            if not CheckDict(data_dict[key], constrain, f'{data_dict_name}.{key}'):
                return False
        else:
            logger.error(f'Unexpected constrain type.')
            return False

    return True
